package com.compression.tool.compression;

import com.compression.tool.model.enums.TaskStatus;
import com.compression.tool.model.task.CompressionTask;
import com.compression.tool.util.CompressionUtils;
import com.compression.tool.util.FileUtils;

import java.io.*;
import java.util.List;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * @author 自然醒
 * @version 1.0
 */
public class ZipCompression implements CompressionStrategy {

    private static final Logger logger = Logger.getLogger(ZipCompression.class.getName());

    @Override
    public boolean compress(List<File> files, File outputFile, int compressionLevel, CompressionTask task) {
        logger.info("开始压缩...");
        logger.info("源文件: " + files);
        logger.info("输出文件: " + outputFile);
        logger.info("压缩级别: " + compressionLevel);
        long totalBytes = FileUtils.calculateFileTotalSize(files);
        long processedSize = 0;
        try (FileOutputStream fos = new FileOutputStream(outputFile);
             ZipOutputStream zos = new ZipOutputStream(fos)) {
            zos.setLevel(compressionLevel);
            for (File file : files) {
                //检查文件是否存在
                if (!file.exists()) {
                    logger.warning("文件不存在: " + file);
                    continue;
                }
                //检查任务是否被取消
                if (task != null && task.isCancelled()) {
                    logger.info("zip任务已经被取消");
                    return false;
                }
                //检查文件是否处于暂停
                if (task != null && task.getStatus() == TaskStatus.PAUSED) {
                    logger.info("zip任务被暂停");
                    CompressionUtils.waitForResume(task);
                }
                addToZip(file, "", zos, task);
                processedSize += FileUtils.calculateFileSize(file.toPath());
                if (task != null && processedSize > 0) {
                    double progress = (double) processedSize / totalBytes * 100;
                    task.updateProgress(processedSize);
                    logger.info("压缩进度: " + progress + "%");
                }
                logger.info("已处理文件: " + file);
            }

        } catch (IOException e) {
            logger.severe("ZIP压缩失败: " + e.getMessage());
            return false;
        } catch (InterruptedException e) {
            logger.info("解压任务被中断");
            return false;
        }
        long compressedSize = outputFile.length();
        double compressionRatio = CompressionUtils.calculateCompressionRatio(totalBytes, compressedSize);
        //任务完成的信息
        String message = String.format("ZIP压缩完成: %s (压缩比: %.2f, %s -> %s)",
                outputFile.getName(), compressionRatio,
                FileUtils.formatFileSize(totalBytes),
                FileUtils.formatFileSize(compressedSize));
        if (task != null) {
            CompressionUtils.notifyCompletion(task, true, message);
        }
        logger.info(message);
        return true;
    }

    /**
     * 添加文件至ZIP
     *
     * @param file
     * @param path
     * @param zos
     * @param task
     * @throws IOException
     */
    private void addToZip(File file, String path, ZipOutputStream zos, CompressionTask task) throws IOException, InterruptedException {
        //检查任务状态
        if (task != null && task.isCancelled()) {
            logger.info("zip任务已经被取消");
        }
        if (task != null && task.getStatus() == TaskStatus.PAUSED) {
            logger.info("zip任务被暂停");
            CompressionUtils.waitForResume(task);
        }
        String entryName = path + file.getName();
        //如果是目录
        if (file.isDirectory()) {
            //创建目录
            if (!entryName.endsWith("/")) {
                entryName += "/";
            }
            ZipEntry entry = new ZipEntry(entryName);
            zos.putNextEntry(entry);
            zos.closeEntry();

            //递归处理目录下的文件
            File[] files = file.listFiles();
            if (files != null && files.length > 0) {
                for (File child : files) {
                    addToZip(child, entryName, zos, task);
                }
            } else {
                logger.info("空目录: " + entryName);
            }
        }
        //如果是文件
        else {
            ZipEntry entry = new ZipEntry(entryName);
            entry.setSize(file.length());
            entry.setTime(file.lastModified());
            zos.putNextEntry(entry);
            try (FileInputStream fis = new FileInputStream(file)) {
                byte[] buffer = new byte[8192];
                int bytesRead;
                long fileProcessed = 0;
                while ((bytesRead = fis.read(buffer)) != -1) {
                    if (task != null && task.isCancelled()) {
                        throw new IOException("任务被取消");
                    }
                    if (task != null && task.getStatus() == TaskStatus.PAUSED) {
                        CompressionUtils.waitForResume(task);
                    }
                    zos.write(buffer, 0, bytesRead);
                    fileProcessed += bytesRead;
                    //实时更新进度
                    if (task != null && bytesRead > 0) {
                        task.updateProgress(bytesRead);
                    }
                }
                logger.fine("已处理文件: " + fileProcessed + " bytes");
            }
            zos.closeEntry();
        }

    }

    @Override
    public boolean decompress(File compressedFile, File outputDir, CompressionTask task) {
        logger.info("开始解压...");
        logger.info("压缩文件: " + compressedFile);
        logger.info("输出目录: " + outputDir);
        logger.info("任务: " + task);
        if (!FileUtils.isValidArchive(compressedFile)) {
            logger.warning("无效的压缩文件: " + compressedFile);
            return false;
        }
        if (!CompressionUtils.validateOutputPath(outputDir.getAbsolutePath(), List.of(compressedFile))) {
            logger.warning("输出目录无效: " + outputDir);
            if (task != null) {
                CompressionUtils.notifyCompletion(task, false, "输出目录无效");
            }
            return false;
        }
        try (ZipInputStream zis = new ZipInputStream(new FileInputStream(compressedFile))) {
            //确保存在目录
            if (!outputDir.exists()) {
                outputDir.mkdirs();
            }
            ZipEntry entry;
            // 记录开始时间
            long startTime = System.currentTimeMillis();
            long totalSize = compressedFile.length();
            long processedSize = 0;
            while ((entry = zis.getNextEntry()) != null) {
                if (task != null && task.isCancelled()) {
                    logger.info("解压任务被取消");
                    return false;
                }
                if (task != null && task.getStatus() == TaskStatus.PAUSED) {
                    logger.info("解压任务被暂停");
                    CompressionUtils.waitForResume(task);
                }
                String entryName = entry.getName();
                File outputFile = new File(outputDir, entryName);
                // 安全检查：防止ZIP滑动攻击
                if (!outputFile.toPath().normalize().startsWith(outputDir.toPath().normalize())) {
                    throw new IOException("无效的ZIP条目路径: " + entry.getName());
                }
                if (entry.isDirectory()) {
                    if (!outputFile.mkdirs() && !outputFile.exists()) {
                        logger.warning("无法创建目录: " + outputFile.getAbsolutePath());
                        return false;
                    }
                } else {
                    File parentDir = outputFile.getParentFile();
                    if (parentDir != null && !parentDir.exists() && !parentDir.mkdirs()) {
                        logger.warning("无法创建目录: " + parentDir.getAbsolutePath());
                        return false;
                    }
                    try (FileOutputStream fos = new FileOutputStream(outputFile)) {
                        byte[] buffer = new byte[8192];
                        int length;
                        while ((length = zis.read(buffer)) > 0) {
                            //检查任务状态
                            if (task != null && task.isCancelled()) {
                                logger.info("解压任务被取消");
                                return false;
                            }
                            fos.write(buffer, 0, length);
                            processedSize += length;
                            //更新进度
                            if (task != null && totalSize > 0) {
                                double progress = (double) processedSize / totalSize * 100;
                                task.updateProgress(processedSize);
                            }
                        }
                    }
                }
                zis.closeEntry();
            }
            // 计算性能统计
            long endTime = System.currentTimeMillis();
            long elapsedTime = endTime - startTime;

            logger.info("ZIP解压完成: " + compressedFile.getName());
            logger.info("解压耗时: " + elapsedTime);
            logger.info("输出目录: " + outputDir.getAbsolutePath());

            if (task != null) {
                CompressionUtils.notifyCompletion(task, true, "解压完成");
            }
            return true;
        } catch (IOException e) {
            logger.warning("解压失败: " + e.getMessage());
            if (task != null) {
                CompressionUtils.notifyCompletion(task, false, e.getMessage());
            }
        } catch (InterruptedException e) {
            logger.warning("解压任务被取消");
            if (task != null) {
                CompressionUtils.notifyCompletion(task, false, "解压任务被取消");
            }
        }

        return false;
    }

    @Override
    public String[] getSupportedExtensions() {
        return new String[]{"zip"};
    }

    @Override
    public String getFormatName() {
        return "ZIP";
    }
}
